/*
 * Copyright 2010 - Jeremie Brebec
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.jb.sharedbills.server;

import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.jb.sharedbills.server.model.EventAccess;
import org.jb.sharedbills.server.modules.Memcache;
import org.jb.sharedbills.shared.dto.EventInfo;
import org.jb.sharedbills.shared.dto.WelcomeInfo;
import org.jb.sharedbills.shared.model.Event;
import org.jb.sharedbills.shared.model.MockEventData;
import org.jb.sharedbills.shared.svc.EventService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.inject.Inject;
import com.google.inject.Provider;

/**
 * 
 * @author jeremie
 *
 */
public class EventServiceImpl implements EventService {

	private static final Logger logger = LoggerFactory.getLogger("EventService");
		
	/**
	 * 
	 * @param userService
	 */
	@Inject
	public EventServiceImpl( UserService userService, Provider<EntityManager> entityManagerProvider, @Memcache Map<Object,Object> cache ) {
		this.userService = userService;
		this.entityManagerProvider = entityManagerProvider;
		this.cache = cache;
	}
	
	private String getEventCacheKey( String id ) {
		return "EVT/" + id;
	}
	
	/**
	 * 
	 */
	@Override
	public EventInfo getEventInfo(String id) {
				
		EventInfo info = new EventInfo();
		
		String userID = null;
		
		if (userService.isUserLoggedIn()) {
			User user = userService.getCurrentUser();
			
			userID = user.getUserId();
			
			info.setUser( user.getEmail() );
			info.setUserId( userID );
			
			logger.info( "[{}/{}/{}] requesting event id={}", new Object[]{ user.getEmail(), user.getUserId(), user.getNickname(), id } );
		} else {
			logger.info( "Anonymous user requesting event id={}", id );
			info.setLoginURL( userService.createLoginURL( "/?id=" + id ) );
		}
				
		String cacheKey = getEventCacheKey(id);
		if (cache.containsKey( cacheKey )) {
			logger.info("Cache hit {}", cacheKey );
			EventAccess ea = (EventAccess)cache.get(cacheKey);
						
			info.setEvent(ea.getEvent());
			info.setWriteable( userID == null ? false : ea.canWrite(userID) );
			
			return info;
		}
		else {
			EntityManager em = entityManagerProvider.get();
			try {
				
				Key key = KeyFactory.createKey( EventAccess.class.getSimpleName(), id );
				
				EventAccess ea = em.find(EventAccess.class, key );
								
				if (ea != null) {
					info.setEvent( ea.getEvent() );
				} else {
					
					if ("demo".equals(id)) {
						Event e = MockEventData.createMockEvent();
						
						ea = new EventAccess();
						ea.setKey(key);
						ea.setEvent(e);
																								
						info.setEvent( ea.getEvent() );
					}
					else 
						throw new IllegalArgumentException("Not found");
				}
				
				if ("demo".equals(id)) {
					info.setWriteable(true);
				} else {
					info.setWriteable( userID == null ? false : ea.canWrite(userID) );
				}
				
				cache.put( cacheKey, ea );
				
				return info;
				
			}
			finally {
				em.close();
			}
		}
	}
	
	/**
	 * 
	 */
	@Override
	public String createEvent(String description) {
		
		if (!userService.isUserLoggedIn())
			throw new IllegalArgumentException("Not logged");
		
		User user = userService.getCurrentUser();
		String userID = user.getUserId();
				
		EntityManager em = entityManagerProvider.get();
		
		try {
			
			em.getTransaction().begin();
			
			String randomKey = createRandomString();
			
			EventAccess ea = new EventAccess();
			ea.setKey( KeyFactory.createKey( EventAccess.class.getSimpleName(), randomKey ) );
			ea.setCreator( userID );
			ea.setCreatorEmail( user.getEmail() );
			
			Event event = new Event();
			event.setName( description );
			
			ea.setEvent(event);
			ea.setDescription( description );
			
			em.persist( ea );
			
			em.getTransaction().commit();
			
			cache.remove(getWelcomeInfoCacheKey(userID));

			logger.info( "[{}/{}/{}] creating new event id={}", new Object[]{ user.getEmail(), user.getUserId(), user.getNickname(), randomKey } );
		
			return randomKey;
		}
		finally {
			em.close();
		}
	}

	/**
	 * 
	 */
	@Override
	public void saveEvent(String id, Event event) {
		
		if ("demo".equals(id))
			return;
		
		if (!userService.isUserLoggedIn())
			throw new IllegalArgumentException("Not logged");
		
		User user = userService.getCurrentUser();
		String userID = user.getUserId();
		
		EntityManager em = entityManagerProvider.get();
		
		try {
			
			EventAccess ea = em.find(EventAccess.class, KeyFactory.createKey(EventAccess.class.getSimpleName(), id ) );
			if (ea == null) 
				throw new IllegalArgumentException( "Event not found");
			
			if (!ea.canWrite( userID ) )
				throw new IllegalArgumentException("Not allowed");
			
			em.getTransaction().begin();
			
			ea.setEvent( event );
			ea.setDescription( event.getName() );
			
			em.getTransaction().commit();
			
			cache.remove( getEventCacheKey(id));
			
			logger.info( "[{}/{}/{}] save event id={}", new Object[]{ user.getEmail(), user.getUserId(), user.getNickname(), id } );
		}
		finally {
			em.close();
		}
		
	}
	
	/**
	 * 
	 * @param userID
	 * @return
	 */
	private String getWelcomeInfoCacheKey( String userID ) {
		return "WLC/" + userID;
	}
	
	/**
	 * 
	 */
	@SuppressWarnings("unchecked")
	@Override
	public WelcomeInfo getWelcomeInfo() {
		
		WelcomeInfo welcomeInfo = new WelcomeInfo();
		
		if (userService.isUserLoggedIn()) {
			
			User user = userService.getCurrentUser();
			String userID = user.getUserId();
			
			String cacheKey = getWelcomeInfoCacheKey(userID);
			if (cache.containsKey(cacheKey)) {
				logger.info("Cache hit {}", cacheKey );

				return (WelcomeInfo) cache.get(cacheKey);
			}
			
			EntityManager em = entityManagerProvider.get();
			
			try {
				
				Query query = em.createQuery("SELECT o FROM org.jb.sharedbills.server.model.EventAccess o WHERE o.creator = '" + userID + "'" ); 
				
				for( EventAccess result : (List<EventAccess>) query.getResultList() ) {
					welcomeInfo.getEvents().put( result.getKey().getName(), result.getDescription() );
				}
			}
			finally {
				em.close();
			}
			welcomeInfo.setUserEmail( user.getEmail() );
			welcomeInfo.setLogoutURL( userService.createLogoutURL("/"));
			
			cache.put(cacheKey, welcomeInfo);
		}
		else {
			welcomeInfo.setLoginURL( userService.createLoginURL("/") );
		}
		
		return welcomeInfo;
	}
	
	/**
	 * 
	 * @return
	 */
	private String createRandomString() {
		StringBuilder b = new StringBuilder();
		
		Random r = new Random();
		
		for(  int i = 0; i < RANDOM_STRING_COUNT; ++i ) {
			b.append( charset.charAt( r.nextInt( charset.length() ) ) );
		}
		
		return b.toString();
	}

	
	/**
	 * 
	 */
	private final UserService userService;
	private final Provider<EntityManager> entityManagerProvider;
	private final Map<Object,Object> cache;

	private static final String charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_";
	private static final int RANDOM_STRING_COUNT = 8;

}
